﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Upreader.Application.Model;
using System.Xml.Linq;
using System.Text.RegularExpressions;

namespace Upreader.Application.Default
{
    class NzbParser
    {
        #region Configuration
        
        private static readonly XNamespace NzbNamespace = "http://www.newzbin.com/DTD/2003/nzb";
        private static readonly Regex ClearTitleRegex = new Regex(@"""(.*)""");
        private static readonly Regex ParityFileChecker = new Regex(@"(.*)\.vol(\d*)\+(\d*)\.par2", RegexOptions.IgnoreCase);

        #endregion // Configuration

        #region Fields

        private readonly IDomainContext _context;

        #endregion // Fields

        #region Constructor

        public NzbParser(IDomainContext context)
        {
            if (context == null)
                throw new ArgumentNullException("context");

            _context = context;
        }

        #endregion // Constructor

        #region Methods 

        public void Parse(System.IO.Stream stream, string defaultTitle)
        {
            // validate
            if (stream == null)
                throw new ArgumentNullException("stream");
            if (defaultTitle == null)
                throw new ArgumentNullException("defaultTitle");

            // load
            XDocument document = XDocument.Load(stream);

            // parse the download
            Download download = ParseDownload(defaultTitle, document);

            // include all the files
            ParseFiles(document, download);

            // Fix for setting the postdate of the newly added download to the oldest file
            download.Posted = _context.Files.Find(x => x.DownloadId == download.Id).Max(x => x.Posted);
            _context.Downloads.Update(download);
        }

        private void ParseFiles(XDocument document, Download download)
        {
            IEnumerable<XElement> fileElements = document.Root.Descendants(NzbNamespace + "file");
            int currentFilePriority = 0;

            foreach (XElement fileElement in fileElements)
            {
                string poster = fileElement.Attribute("poster").Value;
                string title = fileElement.Attribute("subject").Value;
                string postedString = fileElement.Attribute("date").Value;
                DateTime posted = ConvertTimestamp(Convert.ToDouble(postedString));

                var groupsQuery = fileElement.Descendants(NzbNamespace + "groups")
                                             .Descendants(NzbNamespace + "group")
                                             .Select(x => x.Value);

                var groups = groupsQuery.ToArray();

                Match clearTitleMatch = ClearTitleRegex.Match(title);
                if (clearTitleMatch.Success)
                {
                    title = clearTitleMatch.Groups[1].Value.Trim();
                }
                else
                {
                    title = title.Trim();
                }

                bool isRepairFile = ParityFileChecker.IsMatch(title);

                File file = new File()
                {
                    DownloadId = download.Id,
                    Poster = poster,
                    Title = title,
                    Inserted = DateTime.UtcNow,
                    Posted = posted,
                    FileName = title,
                    Groups = groups,
                    Priority = currentFilePriority++
                };

                _context.Files.Add(file);

                ParseSegments(fileElement, file);
            }
        }

        private void ParseSegments(XElement fileElement, File file)
        {
            IEnumerable<XElement> segmentElements = fileElement.Descendants(NzbNamespace + "segments")
                                                               .Descendants(NzbNamespace + "segment");

            foreach (XElement segmentElement in segmentElements)
            {
                Segment segment = new Segment()
                {
                    FileId = file.Id,
                    ArticleId = segmentElement.Value,
                    Index = int.Parse(segmentElement.Attribute("number").Value),
                    Length = int.Parse(segmentElement.Attribute("bytes").Value)
                };

                _context.Segments.Add(segment);
            }
        }

        private Download ParseDownload(string defaultTitle, XDocument document)
        {
            string title = document.Descendants(NzbNamespace + "meta")
                                   .Where(x => x.Attribute("type").Value == "title")
                                   .Select(x => x.Value)
                                   .SingleOrDefault();

            if (title == null)
                title = defaultTitle;

            Download download = new Download()
            {
                Inserted = DateTime.UtcNow,
                Title = title
            };

            _context.Downloads.Add(download);

            return download;
        }

        #endregion // Methods

        #region Helpers

        /// <summary>
        /// method for converting a UNIX timestamp to a regular
        /// System.DateTime value (and also to the current local time)
        /// </summary>
        /// <param name="timestamp">value to be converted</param>
        /// <returns>converted DateTime in string format</returns>
        private static DateTime ConvertTimestamp(double timestamp)
        {
            //create a new DateTime value based on the Unix Epoch
            DateTime converted = new DateTime(1970, 1, 1, 0, 0, 0, 0);
            //add the timestamp to the value
            DateTime newDateTime = converted.AddSeconds(timestamp);

            //return the value in string format
            return newDateTime.ToLocalTime();
        }

        #endregion // Helpers
    }
}
